package sk.hackcraft.util.generator.empirical;

import java.util.List;
import java.util.Random;
import sk.hackcraft.util.algorithm.Search;
import sk.hackcraft.util.generator.DiscreteRandom;
import sk.hackcraft.util.generator.SeedGenerator;
import sk.hackcraft.util.generator.empirical.AbstractBuilderItem.EmpiricalItemCreatorDelegate;

public class DiscreteEmpiricalRandom implements DiscreteRandom
{
	/**
	 * Výnimka vyhodená v prípade vloženia nekorektného buildera, ktorého
	 * suma pravdepodobností nedosahuje hodnotu 1
	 */
	public static class TotalProbabilityHasNotBeenReachedException extends RuntimeException
	{
		public TotalProbabilityHasNotBeenReachedException(String message)
		{
			super("Total probability has not been reached: " + message);
		}
	}
	
	private EmpiricalItem[] items;
	private final Random random;
	
	/**
	 * Inicializácia diskrétneho empirického rozdelenia
	 * @param builder valídny builder diskrétneho empirického rozdelenia
	 */
	public DiscreteEmpiricalRandom(DiscreteEmpiricalBuilder builder)
	{
		this(SeedGenerator.getNextSeed(), builder);
	}
	
	public DiscreteEmpiricalRandom(long seed, DiscreteEmpiricalBuilder builder)
	{
		this.random = new Random(SeedGenerator.getNextSeed());
		this.initializeItems(builder);
	}
	
	/**
	 * Spracovanie hodnôt buildera a vytvorenie diskrétnych
	 * rovnomerných rozdelení
	 * @param builder valídny builder empirického rozdelenia
	 */
	private void initializeItems(DiscreteEmpiricalBuilder builder)
	{
		List<AbstractBuilderItem> itemList = builder.getItems();
		double cumulativeValue = 0;
		
		items = new EmpiricalItem[itemList.size()];
		
		for(int i=0; i<itemList.size(); i++)
		{
			AbstractBuilderItem builderItem = itemList.get(i);
			cumulativeValue += builderItem.getProbability();
			
			EmpiricalItemCreatorDelegate delegate = builderItem.getEmpiricalItemCreatorDelegate();
			items[i] = delegate.createEmpiricalItem(cumulativeValue);
		}
		
		if(cumulativeValue < 1)
		{
			throw new TotalProbabilityHasNotBeenReachedException("Reached only "+ cumulativeValue);
		}
	}
	
	/**
	 * Získanie hodnoty diskrétneho empirického rozdelenia
	 * @return nová hodnota diskrétneho empirického rozdelenia
	 */
	@Override
	public int next()
	{
		EmpiricalItem searchItem = EmpiricalItem.getSearchItem(random.nextDouble());
		int position = Search.findSmallestGreater(items, searchItem);
		
		return items[position].nextNumber();
	}
}
